using System.Diagnostics;
using System.Collections.Generic;
using System.Text;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{



///
/// <summary> * Implementation of the node store. </summary>
/// 
	public class PropertyIndexStore : AbstractStore, Store
	{
	// store version, should end with this string (byte encoded)
		private const string VERSION = "PropertyIndex v0.9.5";
		private const int KEY_STORE_BLOCK_SIZE = 30;

	// in_use(byte)+prop_count(int)+key_block_id(int)
		private const int RECORD_SIZE = 9;

		private DynamicStringStore keyPropertyStore;

		public PropertyIndexStore<T1>(string fileName, Map<T1> config) : base(fileName, config)
		{
		}

		public PropertyIndexStore(string fileName) : base(fileName)
		{
		}

		protected internal virtual void initStorage()
		{
			keyPropertyStore = new DynamicStringStore(getStorageFileName() + ".keys", getConfig());
		}

		public virtual string getTypeAndVersionDescriptor()
		{
			return VERSION;
		}

		public override int getRecordSize()
		{
			return RECORD_SIZE;
		}

		protected internal override void setRecovered()
		{
			base.setRecovered();
			keyPropertyStore.setRecovered();
		}

		protected internal override void unsetRecovered()
		{
			base.unsetRecovered();
			keyPropertyStore.unsetRecovered();
		}

		public override void makeStoreOk()
		{
			keyPropertyStore.makeStoreOk();
			base.makeStoreOk();
		}

		public override void rebuildIdGenerators()
		{
			keyPropertyStore.rebuildIdGenerators();
			base.rebuildIdGenerators();
		}

		public virtual void updateIdGenerators()
		{
			keyPropertyStore.updateHighId();
			this.updateHighId();
		}

		public virtual void freeBlockId(int id)
		{
			keyPropertyStore.freeBlockId(id);
		}

		protected internal override void closeStorage()
		{
			keyPropertyStore.Close();
			keyPropertyStore = null;
		}

		public override void flushAll()
		{
			keyPropertyStore.flushAll();
			base.flushAll();
		}

		public static void createStore(string fileName)
		{
			createEmptyStore(fileName, VERSION);
			DynamicStringStore.createStore(fileName + ".keys", KEY_STORE_BLOCK_SIZE);
		}

		public virtual PropertyIndexData[] getPropertyIndexes(int count)
		{
			LinkedList<PropertyIndexData> indexList = new LinkedList<PropertyIndexData>();
			long maxIdInUse = getHighestPossibleIdInUse();
			int found = 0;
			for (int i = 0; i <= maxIdInUse && found < count; i++)
			{
				PropertyIndexRecord record;
				try
				{
					record = getRecord(i);
				}
				catch (InvalidRecordException t)
				{
					continue;
				}
				found++;
				indexList.AddLast(new PropertyIndexData(record.getId(), getStringFor(record)));
			}
			return indexList.ToArray(new PropertyIndexData[indexList.Count]);
		}

		public virtual PropertyIndexData getPropertyIndex(int id)
		{
			PropertyIndexRecord record = getRecord(id);
			return new PropertyIndexData(record.getId(), getStringFor(record));
		}

		public virtual PropertyIndexData getPropertyIndex(int id, bool recovered)
		{
			Debug.Assert(recovered);
			try
			{
				setRecovered();
				PropertyIndexRecord record = getRecord(id);
				return new PropertyIndexData(record.getId(), getStringFor(record));
			}
			finally
			{
				unsetRecovered();
			}
		}

		public virtual PropertyIndexRecord getRecord(int id)
		{
			PropertyIndexRecord record;
			PersistenceWindow window = acquireWindow(id, OperationType.READ);
			try
			{
				record = getRecord(id, window);
			}
			finally
			{
				releaseWindow(window);
			}
			Collection<DynamicRecord> keyRecords = keyPropertyStore.getLightRecords(record.getKeyBlockId());
			foreach (DynamicRecord keyRecord in keyRecords)
			{
				record.addKeyRecord(keyRecord);
			}
			return record;
		}

		public virtual PropertyIndexRecord getLightRecord(int id)
		{
			PersistenceWindow window = acquireWindow(id, OperationType.READ);
			try
			{
				PropertyIndexRecord record = getRecord(id, window);
				record.setIsLight(true);
				return record;
			}
			finally
			{
				releaseWindow(window);
			}
		}

		public virtual void updateRecord(PropertyIndexRecord record, bool recovered)
		{
			Debug.Assert(recovered);
			setRecovered();
			try
			{
				updateRecord(record);
			}
			finally
			{
				unsetRecovered();
			}
		}

		public virtual void updateRecord(PropertyIndexRecord record)
		{
			PersistenceWindow window = acquireWindow(record.getId(), OperationType.WRITE);
			try
			{
				updateRecord(record, window);
			}
			finally
			{
				releaseWindow(window);
			}
			if (!record.isLight())
			{
				foreach (DynamicRecord keyRecord in record.getKeyRecords())
				{
					keyPropertyStore.updateRecord(keyRecord);
				}
			}
		}

		public virtual Collection<DynamicRecord> allocateKeyRecords(int keyBlockId, char[] chars)
		{
			return keyPropertyStore.allocateRecords(keyBlockId, chars);
		}

		public virtual int nextKeyBlockId()
		{
			return keyPropertyStore.nextBlockId();
		}

		private PropertyIndexRecord getRecord(int id, PersistenceWindow window)
		{
			Buffer buffer = window.getOffsettedBuffer(id);
			bool inUse = (buffer.get() == Record.IN_USE.byteValue());
			if (!inUse)
			{
				throw new InvalidRecordException("Record[" + id + "] not in use");
			}
			PropertyIndexRecord record = new PropertyIndexRecord(id);
			record.setInUse(inUse);
			record.setPropertyCount(buffer.getInt());
			record.setKeyBlockId(buffer.getInt());
			return record;
		}

		private void updateRecord(PropertyIndexRecord record, PersistenceWindow window)
		{
			int id = record.getId();
			Buffer buffer = window.getOffsettedBuffer(id);
			if (record.inUse())
			{
				buffer.put(Record.IN_USE.byteValue()).putInt(record.getPropertyCount()).putInt(record.getKeyBlockId());
			}
			else
			{
				buffer.put(Record.NOT_IN_USE.byteValue());
				if (!isInRecoveryMode())
				{
					freeId(id);
				}
			}
		}

		public virtual void makeHeavy(PropertyIndexRecord record)
		{
			record.setIsLight(false);
			Collection<DynamicRecord> keyRecords = keyPropertyStore.getRecords(record.getKeyBlockId());
			foreach (DynamicRecord keyRecord in keyRecords)
			{
				record.addKeyRecord(keyRecord);
			}
		}

		public virtual string getStringFor(PropertyIndexRecord propRecord)
		{
			int recordToFind = propRecord.getKeyBlockId();
			IEnumerator <DynamicRecord> records = propRecord.getKeyRecords().GetEnumerator();
			List<char[]> charList = new LinkedList<char[]>();
			int totalSize = 0;
			while (recordToFind != Record.NO_NEXT_BLOCK.intValue() && records.MoveNext())
			{
				DynamicRecord record = records.Current;
				if (record.inUse() && record.getId() == recordToFind)
				{
					if (record.isLight())
					{
						keyPropertyStore.makeHeavy(record);
					}
					if (!record.isCharData())
					{
						ByteBuffer buf = ByteBuffer.wrap(record.getData());
						char[] chars = new char[record.getData().length / 2];
						totalSize += chars.Length;
						buf.asCharBuffer().get(chars);
						charList.Add(chars);
					}
					else
					{
						charList.Add(record.getDataAsChar());
					}
					recordToFind = record.getNextBlock();
				// TODO: optimize here, high chance next is right one
					records = propRecord.getKeyRecords().GetEnumerator();
				}
			}
			StringBuilder buf = new StringBuilder();
			foreach (char[] str in charList)
			{
				buf.Append(str);
			}
			return buf.ToString();
		}

		public override string ToString()
		{
			return "PropertyIndexStore";
		}

		protected internal override bool versionFound(string version)
		{
			if (!version.StartsWith("PropertyIndex"))
			{
			// non clean shutdown, need to do recover with right neo
				return false;
			}
			if (version.Equals("PropertyIndex v0.9.3"))
			{
				rebuildIdGenerator();
				closeIdGenerator();
				return true;
			}
			throw new IllegalStoreVersionException("Store version [" + version + "]. Please make sure you are not running old Neo4j kernel " + " towards a store that has been created by newer version " + " of Neo4j.");
		}
	}
}